1. Introduction/Business Understanding

1.1 Description of the problem

In [277]:
import pandas as pd
import numpy as np
import geopandas as gpd
import requests
from bs4 import BeautifulSoup
import os
from sklearn.cluster import KMeans
from sklearn.preprocessing import StandardScaler
from sklearn.metrics import silhouette_score
import folium
from folium.plugins import MarkerCluster
from geopy.geocoders import Nominatim 
import matplotlib.cm as cm
import matplotlib.colors as colors
import matplotlib.pyplot as plt
In [8]:
url = "https://en.wikipedia.org/wiki/Busan"
extracting_data = requests.get(url).text
tds = BeautifulSoup(extracting_data, 'lxml').find('table', {'class': 'sortable'}).findAll('td')
busan_rows = []
busan_columns = ['Subdivision', 'Korean', 'Area', 'Population']

for i in range(0, len(tds), 4):
    busan_rows.append({
                 busan_columns[0]:tds[i].text.strip(), 
                 busan_columns[1]:tds[i+1].text.strip(), 
                 busan_columns[2]:tds[i+2].text.strip(),
                 busan_columns[3]:int((tds[i+3].text.strip()).replace(',', ''))
                })
busan_data = pd.DataFrame(busan_rows, columns=busan_columns)
busan_data = busan_data.drop(busan_data[['Korean']], axis=1)

busan_data
Out[8]:
Subdivision Area Population
0 Buk-gu 39.36 303955
1 Busanjin-gu 29.70 372922
2 Dong-gu 9.73 90668
3 Dongnae-gu 16.63 271350
4 Gangseo-gu 181.50 123636
5 Geumjeong-gu 65.27 249054
6 Haeundae-gu 51.47 417174
7 Jung-gu 2.83 45821
8 Nam-gu 26.81 278681
9 Saha-gu 41.75 337423
10 Sasang-gu 36.09 233443
11 Seo-gu 13.93 111906
12 Suyeong-gu 10.21 181526
13 Yeongdo-gu 14.15 124918
14 Yeonje-gu 12.08 207396
15 Gijang-gun 218.32 164546
In [9]:
nil = gpd.read_file('busan.geojson')
nil = nil.drop(nil[['SIG_CD']], axis=1)
nil = nil.rename(columns = {'SIG_ENG_NM':'Subdivision', 'SIG_KOR_NM':'Korean'})

# Merge Busan geojson and table
busan_data = nil.merge(busan_data, on='Subdivision')
In [10]:
busan_data
Out[10]:
Subdivision Korean geometry Area Population
0 Jung-gu 중구 MULTIPOLYGON (((129.03231 35.11643, 129.03235 ... 2.83 45821
1 Seo-gu 서구 MULTIPOLYGON (((129.01542 35.04808, 129.01515 ... 13.93 111906
2 Dong-gu 동구 MULTIPOLYGON (((129.04264 35.14589, 129.04327 ... 9.73 90668
3 Yeongdo-gu 영도구 MULTIPOLYGON (((129.09320 35.03771, 129.09324 ... 14.15 124918
4 Busanjin-gu 부산진구 MULTIPOLYGON (((129.04001 35.19981, 129.04033 ... 29.70 372922
5 Dongnae-gu 동래구 MULTIPOLYGON (((129.07905 35.22509, 129.07910 ... 16.63 271350
6 Nam-gu 남구 MULTIPOLYGON (((129.12702 35.09096, 129.12697 ... 26.81 278681
7 Buk-gu 북구 MULTIPOLYGON (((128.98774 35.20145, 128.98774 ... 39.36 303955
8 Haeundae-gu 해운대구 MULTIPOLYGON (((129.13898 35.15860, 129.13963 ... 51.47 417174
9 Saha-gu 사하구 MULTIPOLYGON (((128.95633 34.88970, 128.95609 ... 41.75 337423
10 Geumjeong-gu 금정구 MULTIPOLYGON (((129.10621 35.30646, 129.10640 ... 65.27 249054
11 Gangseo-gu 강서구 MULTIPOLYGON (((128.77648 35.01114, 128.77625 ... 181.50 123636
12 Yeonje-gu 연제구 MULTIPOLYGON (((129.07817 35.19945, 129.07827 ... 12.08 207396
13 Suyeong-gu 수영구 MULTIPOLYGON (((129.11682 35.18344, 129.11689 ... 10.21 181526
14 Sasang-gu 사상구 MULTIPOLYGON (((128.99094 35.19385, 128.99121 ... 36.09 233443
15 Gijang-gun 기장군 MULTIPOLYGON (((129.22953 35.21716, 129.22938 ... 218.32 164546
In [366]:
def build_map(data, map_type):
    busan = 'Busan, Korea'

    geolocator = Nominatim(user_agent='busan_explorer')
    latitude = geolocator.geocode(busan).latitude
    longitude = geolocator.geocode(busan).longitude

    busan_map = folium.Map(
        location=[latitude, longitude],
        zoom_start=11 
    )
    
    if map_type == 'default':
        busan_map.choropleth(
            geo_data=data,
            data=data,
            key_on='feature.properties.Subdivision',
            columns= ['Subdivision', 'Population'],
            fill_color='YlOrRd', 
            fill_opacity=0.7, 
            line_opacity=0.2,
            legend_name='Population in Busan'
        )
    else:
        busan_map.choropleth(
            geo_data=data,
            data=data,
            key_on='feature.properties.Subdivision',
            columns=['Subdivision', 'Cluster Labels'],
            fill_color='Pastel1', 
            fill_opacity=0.7, 
            line_opacity=0.3,
            threshold_scale=[0, 1, 2, 3, 4],
            legend_name='Cluster Labels'
        )
       
    return busan_map

def build_tooltip(busan_map, data, fields, aliases):
    style_function = lambda x: {'fillColor': '#ffffff', 
                            'color':'#000000', 
                            'fillOpacity': 0.1, 
                            'weight': 0.1}
    highlight_function = lambda x: {'fillColor': '#000000', 
                                'color':'#000000', 
                                'fillOpacity': 0.50, 
                                'weight': 0.1}
    geo = folium.GeoJson(
            data,
            style_function=style_function, 
            control=False,
            highlight_function=highlight_function, 
            tooltip=folium.features.GeoJsonTooltip(
                    fields=fields,
                    aliases=aliases,
                    style=("background-color: white; color: #333333; font-family: arial; font-size: 12px; padding: 10px;") 
                   )
                )
    busan_map.add_child(geo)
    busan_map.keep_in_front(geo)
    folium.LayerControl().add_to(busan_map)
In [363]:
busan_map = build_map(busan_data, 'default')
tooltip = build_tooltip(busan_map, busan_data, ['Subdivision', 'Population'], ['Subdivision: ','Population: '])

busan_map
Out[363]:
Make this Notebook Trusted to load map: File -> Trust Notebook
In [13]:
CLIENT_ID = 'VF0AUJWBTE2RYQHKRCCI31WJEVCKTRR4DTQOBEPBXIZYPLK2' 
CLIENT_SECRET = 'MUGUC4RBSDWTLI4W2JSYO2Y2IDCCUOEXRGXVWJSLP11Y03XM'
VERSION = '20200711'
RADIUS = 1000
LIMIT = 100
In [15]:
def getNearbyVenues(subdivision, korean):
    venues = []
    for sub, kor in zip(subdivision, korean):
        busa_kor = '부산광역시, 대한민국'
        url = 'https://api.foursquare.com/v2/venues/explore?&client_id={}&client_secret={}&v={}&near={}, {}'.format(
            CLIENT_ID, 
            CLIENT_SECRET, 
            VERSION, 
            kor,
            busa_kor
             )
                
        results = requests.get(url).json()["response"]['groups'][0]['items']
        
        venues.append([(
            sub, 
            v['venue']['name'], 
            v['venue']['location']['lat'], 
            v['venue']['location']['lng'],  
            v['venue']['categories'][0]['name']) for v in results])

        nearby_venues = pd.DataFrame([item for venues in venues for item in venues])
        nearby_venues.columns = ['Subdivision', 
                  'Venue', 
                  'Venue Latitude', 
                  'Venue Longitude', 
                  'Venue Category']
        
    return (nearby_venues)
    
In [16]:
busan_venues = getNearbyVenues(subdivision=busan_data['Subdivision'], 
                               korean=busan_data['Korean'])
print(busan_venues.shape)
busan_venues.hean()
Out[16]:
Subdivision Venue Venue Latitude Venue Longitude Venue Category
0 Jung-gu 이재모피자 35.102056 129.030717 Pizza Place
1 Jung-gu 화국반점 35.102683 129.034004 Chinese Restaurant
2 Jung-gu Golmok Gejang (골목게장) 35.108443 129.038069 Korean Restaurant
3 Jung-gu Starbucks (스타벅스) 35.105049 129.036298 Coffee Shop
4 Jung-gu Busan Tower (부산타워) 35.100929 129.032514 Scenic Lookout
... ... ... ... ... ...
459 Gijang-gun Starbucks 35.376110 129.155456 Coffee Shop
460 Gijang-gun 천지할매집 35.219452 129.227557 Seafood Restaurant
461 Gijang-gun Shinsegae Busan Premium Outlet (부산프리미엄아울렛) 35.322645 129.235463 Outlet Mall
462 Gijang-gun 메가마트 MEGA MART (메가마트) 35.254592 129.222630 Grocery Store
463 Gijang-gun Haeundae Country Club (해운대CC) 35.364416 129.201708 Golf Course

464 rows × 5 columns

In [22]:
venues = busan_venues.groupby('Venue Category').count().sort_values(by='Venue', ascending=False).head(10)
venues.plot.bar(y="Venue", use_index=True, rot=70, title="Top 10 highest number of venues in Busan", figsize=(15,5));
In [24]:
print('There are {} uniques categories.'.format(len(busan_venues['Venue Category'].unique())))
There are 84 uniques categories.
In [25]:
# one hot encoding
busan_onehot = pd.get_dummies(busan_venues[['Venue Category']], prefix="", prefix_sep="")

# add neighborhood column back to dataframe
busan_onehot['Subdivision'] = busan_venues['Subdivision'] 

# move neighborhood column to the first column
busan_onehot = busan_onehot[['Subdivision'] + [col for col in busan_onehot.columns if col != 'Subdivision']]
print(busan_onehot.shape)
busan_onehot.head()
(464, 85)
Out[25]:
Subdivision Airport Airport Lounge Art Gallery Asian Restaurant BBQ Joint Bakery Bar Baseball Field Beach ... Spa Steakhouse Supermarket Sushi Restaurant Theme Park Toll Plaza Trail Train Station Turkish Restaurant Used Bookstore
0 Jung-gu 0 0 0 0 0 0 0 0 0 ... 0 0 0 0 0 0 0 0 0 0
1 Jung-gu 0 0 0 0 0 0 0 0 0 ... 0 0 0 0 0 0 0 0 0 0
2 Jung-gu 0 0 0 0 0 0 0 0 0 ... 0 0 0 0 0 0 0 0 0 0
3 Jung-gu 0 0 0 0 0 0 0 0 0 ... 0 0 0 0 0 0 0 0 0 0
4 Jung-gu 0 0 0 0 0 0 0 0 0 ... 0 0 0 0 0 0 0 0 0 0

5 rows × 85 columns

In [150]:
busan_grouped = busan_onehot.groupby('Subdivision').sum().reset_index()
print(busan_grouped.shape)
busan_grouped.head()
(16, 85)
Out[150]:
Subdivision Airport Airport Lounge Art Gallery Asian Restaurant BBQ Joint Bakery Bar Baseball Field Beach ... Spa Steakhouse Supermarket Sushi Restaurant Theme Park Toll Plaza Trail Train Station Turkish Restaurant Used Bookstore
0 Buk-gu 0 0 0 0 0 5 0 0 0 ... 0 0 1 0 0 0 0 1 0 0
1 Busanjin-gu 0 0 0 0 2 0 0 0 0 ... 0 0 0 0 0 0 0 0 0 0
2 Dong-gu 0 0 0 0 0 1 0 0 0 ... 0 0 0 0 0 0 1 1 0 0
3 Dongnae-gu 0 0 0 0 0 1 0 0 0 ... 0 1 2 0 0 0 1 0 0 0
4 Gangseo-gu 1 2 1 0 1 1 0 0 0 ... 0 0 4 0 0 0 0 0 0 0

5 rows × 85 columns

In [28]:
def return_most_common_venues(row, num_top_venues):
    row_categories = row.iloc[1:]
    row_categories_sorted = row_categories.sort_values(ascending=False)
    
    return row_categories_sorted.index.values[0:num_top_venues]
In [253]:
num_top_venues = 10

indicators = ['st', 'nd', 'rd']

# create columns according to number of top venues
columns = ['Subdivision']
for ind in np.arange(num_top_venues):
    try:
        columns.append('{}{} Most Common Venue'.format(ind+1, indicators[ind]))
    except:
        columns.append('{}th Most Common Venue'.format(ind+1))

# create a new dataframe
subdivision_venues_sorted = pd.DataFrame(columns=columns)
subdivision_venues_sorted['Subdivision'] = busan_grouped['Subdivision']

for ind in np.arange(busan_grouped.shape[0]):
    subdivision_venues_sorted.iloc[ind, 1:] = return_most_common_venues(busan_grouped.iloc[ind, :], num_top_venues)

subdivision_venues_sorted.head()
Out[253]:
Subdivision 1st Most Common Venue 2nd Most Common Venue 3rd Most Common Venue 4th Most Common Venue 5th Most Common Venue 6th Most Common Venue 7th Most Common Venue 8th Most Common Venue 9th Most Common Venue 10th Most Common Venue
0 Buk-gu Bakery Coffee Shop Fast Food Restaurant Korean Restaurant Ice Cream Shop Park Multiplex Market Dumpling Restaurant Hotel
1 Busanjin-gu Coffee Shop Korean Restaurant Italian Restaurant Noodle House BBQ Joint Hotel Park Concert Hall Multiplex Department Store
2 Dong-gu Korean Restaurant Gukbap Restaurant Hotel Café Coffee Shop Chinese Restaurant Scenic Lookout Park Trail Bakery
3 Dongnae-gu Coffee Shop Korean Restaurant Supermarket Hotel Market Department Store Fried Chicken Joint Big Box Store Noodle House Dumpling Restaurant
4 Gangseo-gu Supermarket Fast Food Restaurant Korean Restaurant Coffee Shop Airport Lounge Ice Cream Shop Airport Racecourse Hotel Duty-free Shop
In [276]:
busan_grouped_clustering = busan_grouped.drop('Subdivision', 1)
Clus_dataSet = StandardScaler().fit_transform(busan_grouped_clustering)

distortion = []

for k in range(2, 6):
    kmeans = KMeans(n_clusters = k).fit(Clus_dataSet)  
    predict = kmeans.fit_predict(Clus_dataSet)
    distortion.append(silhouette_score(Clus_dataSet, predict, metric = 'euclidean'))

plt.plot(range(2, 6), distortion)
plt.title('Silhouette Method')
plt.xlabel('Number of clusters')
plt.show()
In [254]:
# Optimal K is 4
kmeans = KMeans(n_clusters=4, random_state=0).fit(Clus_dataSet)
kmeans.labels_
subdivision_venues_sorted.insert(0, 'Cluster Labels', kmeans.labels_)
busan_merged = pd.merge(busan_data, subdivision_venues_sorted.set_index('Subdivision'), on='Subdivision')
In [255]:
busan_merged
Out[255]:
Subdivision Korean geometry Area Population Cluster Labels 1st Most Common Venue 2nd Most Common Venue 3rd Most Common Venue 4th Most Common Venue 5th Most Common Venue 6th Most Common Venue 7th Most Common Venue 8th Most Common Venue 9th Most Common Venue 10th Most Common Venue
0 Jung-gu 중구 MULTIPOLYGON (((129.03231 35.11643, 129.03235 ... 2.83 45821 0 Coffee Shop Korean Restaurant Hotel Market Park Used Bookstore Public Art Japanese Restaurant Department Store Multiplex
1 Seo-gu 서구 MULTIPOLYGON (((129.01542 35.04808, 129.01515 ... 13.93 111906 0 Coffee Shop Korean Restaurant BBQ Joint Market Used Bookstore Noodle House Chinese Restaurant Fried Chicken Joint Pizza Place Plaza
2 Dong-gu 동구 MULTIPOLYGON (((129.04264 35.14589, 129.04327 ... 9.73 90668 0 Korean Restaurant Gukbap Restaurant Hotel Café Coffee Shop Chinese Restaurant Scenic Lookout Park Trail Bakery
3 Yeongdo-gu 영도구 MULTIPOLYGON (((129.09320 35.03771, 129.09324 ... 14.15 124918 2 Korean Restaurant Beach Scenic Lookout Trail Café Chinese Restaurant Coffee Shop Pier Deli / Bodega Port
4 Busanjin-gu 부산진구 MULTIPOLYGON (((129.04001 35.19981, 129.04033 ... 29.70 372922 0 Coffee Shop Korean Restaurant Italian Restaurant Noodle House BBQ Joint Hotel Park Concert Hall Multiplex Department Store
5 Dongnae-gu 동래구 MULTIPOLYGON (((129.07905 35.22509, 129.07910 ... 16.63 271350 0 Coffee Shop Korean Restaurant Supermarket Hotel Market Department Store Fried Chicken Joint Big Box Store Noodle House Dumpling Restaurant
6 Nam-gu 남구 MULTIPOLYGON (((129.12702 35.09096, 129.12697 ... 26.81 278681 1 Ice Cream Shop Fast Food Restaurant Gukbap Restaurant Korean Restaurant Bakery Coffee Shop Seafood Restaurant Concert Hall Japanese Restaurant Ramen Restaurant
7 Buk-gu 북구 MULTIPOLYGON (((128.98774 35.20145, 128.98774 ... 39.36 303955 1 Bakery Coffee Shop Fast Food Restaurant Korean Restaurant Ice Cream Shop Park Multiplex Market Dumpling Restaurant Hotel
8 Haeundae-gu 해운대구 MULTIPOLYGON (((129.13898 35.15860, 129.13963 ... 51.47 417174 0 BBQ Joint Korean Restaurant Hotel Coffee Shop Bar Seafood Restaurant Hostel Buffet Dumpling Restaurant Japanese Restaurant
9 Saha-gu 사하구 MULTIPOLYGON (((128.95633 34.88970, 128.95609 ... 41.75 337423 1 Coffee Shop Bakery Fast Food Restaurant Donut Shop Ice Cream Shop Food Court Beach Café Outlet Store Metro Station
10 Geumjeong-gu 금정구 MULTIPOLYGON (((129.10621 35.30646, 129.10640 ... 65.27 249054 1 Coffee Shop Café Korean Restaurant Bus Station Supermarket Fast Food Restaurant Turkish Restaurant Department Store Bus Line Moroccan Restaurant
11 Gangseo-gu 강서구 MULTIPOLYGON (((128.77648 35.01114, 128.77625 ... 181.50 123636 1 Supermarket Fast Food Restaurant Korean Restaurant Coffee Shop Airport Lounge Ice Cream Shop Airport Racecourse Hotel Duty-free Shop
12 Yeonje-gu 연제구 MULTIPOLYGON (((129.07817 35.19945, 129.07827 ... 12.08 207396 1 Coffee Shop Fast Food Restaurant Supermarket Intersection Bakery Baseball Field Metro Station Donut Shop Outdoor Sculpture Multiplex
13 Suyeong-gu 수영구 MULTIPOLYGON (((129.11682 35.18344, 129.11689 ... 10.21 181526 1 Coffee Shop Seafood Restaurant Korean Restaurant BBQ Joint Bar Brewery Park Multiplex Performing Arts Venue Japanese Restaurant
14 Sasang-gu 사상구 MULTIPOLYGON (((128.99094 35.19385, 128.99121 ... 36.09 233443 1 Supermarket Korean Restaurant Coffee Shop Fast Food Restaurant Airport Lounge Airport Park Hotel Ice Cream Shop Duty-free Shop
15 Gijang-gun 기장군 MULTIPOLYGON (((129.22953 35.21716, 129.22938 ... 218.32 164546 3 Coffee Shop Seafood Restaurant Golf Course Korean Restaurant Park Beach Cemetery Market Monument / Landmark Outlet Mall
In [256]:
busan_merged.loc[busan_merged['Cluster Labels'] == 0, busan_merged.columns[[0] + list(range(6, busan_merged.shape[1]))]]
Out[256]:
Subdivision 1st Most Common Venue 2nd Most Common Venue 3rd Most Common Venue 4th Most Common Venue 5th Most Common Venue 6th Most Common Venue 7th Most Common Venue 8th Most Common Venue 9th Most Common Venue 10th Most Common Venue
0 Jung-gu Coffee Shop Korean Restaurant Hotel Market Park Used Bookstore Public Art Japanese Restaurant Department Store Multiplex
1 Seo-gu Coffee Shop Korean Restaurant BBQ Joint Market Used Bookstore Noodle House Chinese Restaurant Fried Chicken Joint Pizza Place Plaza
2 Dong-gu Korean Restaurant Gukbap Restaurant Hotel Café Coffee Shop Chinese Restaurant Scenic Lookout Park Trail Bakery
4 Busanjin-gu Coffee Shop Korean Restaurant Italian Restaurant Noodle House BBQ Joint Hotel Park Concert Hall Multiplex Department Store
5 Dongnae-gu Coffee Shop Korean Restaurant Supermarket Hotel Market Department Store Fried Chicken Joint Big Box Store Noodle House Dumpling Restaurant
8 Haeundae-gu BBQ Joint Korean Restaurant Hotel Coffee Shop Bar Seafood Restaurant Hostel Buffet Dumpling Restaurant Japanese Restaurant
In [257]:
busan_merged.loc[busan_merged['Cluster Labels'] == 1, busan_merged.columns[[0] + list(range(6, busan_merged.shape[1]))]]
Out[257]:
Subdivision 1st Most Common Venue 2nd Most Common Venue 3rd Most Common Venue 4th Most Common Venue 5th Most Common Venue 6th Most Common Venue 7th Most Common Venue 8th Most Common Venue 9th Most Common Venue 10th Most Common Venue
6 Nam-gu Ice Cream Shop Fast Food Restaurant Gukbap Restaurant Korean Restaurant Bakery Coffee Shop Seafood Restaurant Concert Hall Japanese Restaurant Ramen Restaurant
7 Buk-gu Bakery Coffee Shop Fast Food Restaurant Korean Restaurant Ice Cream Shop Park Multiplex Market Dumpling Restaurant Hotel
9 Saha-gu Coffee Shop Bakery Fast Food Restaurant Donut Shop Ice Cream Shop Food Court Beach Café Outlet Store Metro Station
10 Geumjeong-gu Coffee Shop Café Korean Restaurant Bus Station Supermarket Fast Food Restaurant Turkish Restaurant Department Store Bus Line Moroccan Restaurant
11 Gangseo-gu Supermarket Fast Food Restaurant Korean Restaurant Coffee Shop Airport Lounge Ice Cream Shop Airport Racecourse Hotel Duty-free Shop
12 Yeonje-gu Coffee Shop Fast Food Restaurant Supermarket Intersection Bakery Baseball Field Metro Station Donut Shop Outdoor Sculpture Multiplex
13 Suyeong-gu Coffee Shop Seafood Restaurant Korean Restaurant BBQ Joint Bar Brewery Park Multiplex Performing Arts Venue Japanese Restaurant
14 Sasang-gu Supermarket Korean Restaurant Coffee Shop Fast Food Restaurant Airport Lounge Airport Park Hotel Ice Cream Shop Duty-free Shop
In [258]:
busan_merged.loc[busan_merged['Cluster Labels'] == 2, busan_merged.columns[[0] + list(range(6, busan_merged.shape[1]))]]
Out[258]:
Subdivision 1st Most Common Venue 2nd Most Common Venue 3rd Most Common Venue 4th Most Common Venue 5th Most Common Venue 6th Most Common Venue 7th Most Common Venue 8th Most Common Venue 9th Most Common Venue 10th Most Common Venue
3 Yeongdo-gu Korean Restaurant Beach Scenic Lookout Trail Café Chinese Restaurant Coffee Shop Pier Deli / Bodega Port
In [259]:
busan_map_data = pd.merge(busan_merged, busan_venues, on='Subdivision')
In [367]:
busan_map = build_map(busan_merged, 'mixed')
tooltip = build_tooltip(busan_map, busan_merged, ['Subdivision', 'Population', 'Cluster Labels'], ['Subdivision: ','Population: ', 'Cluster: '])

marker_cluster = MarkerCluster(
    name='Venues cluster',
    overlay=True,
    control=False,
    icon_create_function=None
)

latituds = busan_map_data['Venue Latitude']
longitudes = busan_map_data['Venue Longitude']
subdivisions = busan_map_data['Subdivision']
categoris = busan_map_data['Venue Category']
labels = busan_map_data['Cluster Labels']


for lat, lon, sub, category, cluster in zip(latituds, longitudes, subdivisions, categoris, labels):
    label = folium.Popup('<strong>{}</strong><br><strong>Category</strong>: {}<br><strong>Cluster</strong>: {}'.format(sub, category, cluster), max_width=2650)
    
    marker = folium.Marker(
        location=[lat, lon],
        popup=label,
        icon=None,
        )
    marker_cluster.add_child(marker)
marker_cluster.add_to(busan_map)
busan_map
Out[367]:
Make this Notebook Trusted to load map: File -> Trust Notebook
In [ ]: